﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using TinyROS.Messages;
using TinyROS.Exceptions;
using System.Net.Sockets;
using System.IO;
using System.Text.Json;

namespace TinyROS
{
    public class ApplicationNode : Node, IDisposable, ICommandInteractive
    {
        private Subscriber<CarStatusMessage> StatusSubscriber;
        private Subscriber<MapMessage> MapSubscriber;
        private Publisher<Vector3DMessage> GoalPublisher;
        private Publisher<StringMessage> PlannerCommandPublisher;
        private Dictionary<Publisher, string> pubedTopicName = new();
        private Dictionary<Subscriber, string> subedTopicName = new();
        private HttpListener listener = new();
        private CancellationTokenSource listenerCanceller;
        private CarStatus LatestStatus;
        private Atlas LatestMap;
        private enum ServiceID { GetStatus, GetMap, SetGoal }
        public ApplicationNode() : 
            this(TinyROSConfig.DefaultTopicName.CarStatus, 
                TinyROSConfig.DefaultTopicName.Map, 
                TinyROSConfig.DefaultTopicName.Goal,
                TinyROSConfig.DefaultTopicName.NodeCommand.Planner)
        {

        }
        public ApplicationNode(string statusSubName, string mapSubName, string goalPubName,string plannerCommandPubName)
        {
            this.GoalPublisher = new();
            this.PlannerCommandPublisher = new();
            this.StatusSubscriber = new(this.StoreStatus);
            this.MapSubscriber = new(this.StoreMap);
            this.PublishedTopicName.Add(this.GoalPublisher, goalPubName);
            this.PublishedTopicName.Add(this.PlannerCommandPublisher, plannerCommandPubName);
            this.SubscribedTopicName.Add(this.StatusSubscriber, statusSubName);
            this.SubscribedTopicName.Add(this.MapSubscriber, mapSubName);
        }
        public override Dictionary<Publisher, string> PublishedTopicName { get => this.pubedTopicName; }
        public override Dictionary<Subscriber, string> SubscribedTopicName { get => this.subedTopicName; }
        private bool running = false;
        public override bool IsRunning => this.running;

        public override void Init(SystemCore master)
        {
            var goalTopic = master.RequestTopic<Vector3DTopic>(this.PublishedTopicName[this.GoalPublisher]);
            this.GoalPublisher.SetTopic(goalTopic);
            var statusTopic = master.RequestTopic<CarStatusTopic>(this.SubscribedTopicName[this.StatusSubscriber]);
            this.StatusSubscriber.SetTopic(statusTopic);
            var mapTopic = master.RequestTopic<MapTopic>(this.SubscribedTopicName[this.MapSubscriber]);
            this.MapSubscriber.SetTopic(mapTopic);
            this.ServerInit();
            this.Initialized = true;
        }

        public override void Run()
        {
            this.StatusSubscriber.Start(Subscriber<CarStatusMessage>.SubscribeType.Passive);
            this.MapSubscriber.Start(Subscriber<MapMessage>.SubscribeType.Passive);
            this.listenerCanceller = new();
            this.ServerStart();
            this.running = true;
        }

        public override void Stop()
        {
            this.StatusSubscriber.Stop();
            this.MapSubscriber.Stop();
            try
            {
                this.listenerCanceller.Cancel();
                this.listener.Stop();
            }
            catch
            {

            }
            this.running = false;
        }
        private void ServerInit()
        {
            this.listenerCanceller = new CancellationTokenSource();
            string name = Dns.GetHostName();
            IPAddress[] ipadrlist = Dns.GetHostAddresses(name);
            foreach (IPAddress ipa in ipadrlist)
            {
                if (ipa.AddressFamily == AddressFamily.InterNetwork)
                {
                    this.listener.Prefixes.Add(TinyROSConfig.NetWork.Protocol.Http + ipa.ToString() + TinyROSConfig.NetWork.AppServer.Port + TinyROSConfig.NetWork.AppServer.GetMapURL);
                    this.listener.Prefixes.Add(TinyROSConfig.NetWork.Protocol.Http + ipa.ToString() + TinyROSConfig.NetWork.AppServer.Port + TinyROSConfig.NetWork.AppServer.GetStatusURL);
                    this.listener.Prefixes.Add(TinyROSConfig.NetWork.Protocol.Http + ipa.ToString() + TinyROSConfig.NetWork.AppServer.Port + TinyROSConfig.NetWork.AppServer.PostStringURL);

                }
            }
        }
        private async void ServerStart()
        {
            this.listener.Start();
            try
            {
                await Task.Run(async () =>
                {
                    while (true)
                    {
                        this.listenerCanceller.Token.ThrowIfCancellationRequested();
                        try
                        {
                            var context = await this.listener.GetContextAsync();
                            this.RequestHandler(context);
                        }
                        catch
                        {

                        }
                    }
                   
                }, this.listenerCanceller.Token);
            }
            catch(OperationCanceledException)
            {

            }

        }
        private void RequestHandler(HttpListenerContext context)
        {
            string url = context.Request.RawUrl.ToString();
           // Console.WriteLine(url);
            var response = context.Response;
            switch (url)
            {
                case TinyROSConfig.NetWork.AppServer.GetMapURL:
                    var maps = this.LatestMap;
                    if (maps != null)
                    {
                        var map = maps.Global;
                        var mapC = new Map(map);
                        var mapStream = mapC.PngStream;
                        this.WriteBackAsync(mapStream, context);                      
                    }
                    break;
                case TinyROSConfig.NetWork.AppServer.GetStatusURL:
                    
                    break;
                case TinyROSConfig.NetWork.AppServer.GetServerStatusURL:

                    break;
                case TinyROSConfig.NetWork.AppServer.PostFloatURL:
                    using( var msgS = context.Request.InputStream)
                    {
                        StreamReader reader = new(msgS);
                        var msg = reader.ReadToEnd();
                        var msgObj = JsonSerializer.Deserialize<ClientPostedFloat>(msg);
                        switch (msgObj.msgType) 
                        {
                            case ClientMessageType.SetGoal:

                                break;
                            case ClientMessageType.SetStart:

                                break;
                        }


                    }
                    break;
                case TinyROSConfig.NetWork.AppServer.PostStringURL:

                    break;
                //case TinyROSConfig.NetWork.AppServer.PostGoalURL:
                //    throw new NotImplementedException();
                //    break;
                default:

                    //int l = -1;
                    //if(this.LatestMap!=null)
                    //{
                    //    l = this.LatestMap.Global.Length;
                    //}
                    string responseString = $"<HTML><BODY> Hello world! </BODY></HTML>";
                    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
                    // Get a response stream and write the response to it.
                    response.ContentLength64 = buffer.Length;
                    var outputd = response.OutputStream;
                    outputd.Write(buffer, 0, buffer.Length);
                    // You must close the output stream.
                    outputd.Close();

                    break;
            }
        }
        private async void WriteBackAsync(byte[] stream, HttpListenerContext content)
        {
            using var s = content.Response.OutputStream;
            await s.WriteAsync(stream);
            s.Close();
        }
        private void StoreStatus(CarStatusMessage msg)
        {
            this.LatestStatus = msg.Value;
        }
        private void StoreMap(MapMessage msg)
        {
            this.LatestMap = msg.Value;
        }
        public void Dispose()
        {
            try
            {
                this.listener.Close();
            }
            catch
            {

            }
        }

        public void ExecuteCommand(string cmd, bool raiseError = true)
        {
            throw new NotImplementedException();
        }

        public void ExecuteCommandMute(string cmd, bool raiseError = true)
        {
            throw new NotImplementedException();
        }
        private enum ClientMessageType {Cmd = 0x00, OK = 0x01, SetMotion = 0x02, SetStart = 0x03, 
            SetGoal = 0x04, Msg = 0x07, Error = 0x08, UsrCmd = 0xf0, }
        private class ClientPostedString
        {
            public ClientMessageType msgType { get; set; }
            public string msg { get; set; }
        }
        private class ClientPostedFloat
        {
            public ClientMessageType msgType { get; set; }
            public float item1 { get; set; }
            public float item2 { get; set; }
        }
        private class ClientPostedEmpty
        {
            public ClientMessageType msgType { get; set; }
        }
    }
}
